import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Observable;
import java.util.Vector;

public class Model extends Observable{
	//the recipe has its name as a key 
	private HashMap<String,Recipe> recipes;
	
	private Vector<Action> actions;
	private Plan plan;
	private Agent agent;
		
	/**
	 * No-arg constructor
	 */
	public Model(){
		agent = new Agent();
		setRecipes(new HashMap<String,Recipe>());
		recipes = new generateRecipes().GetRecipes();
	}

	/**
	 * Returns the collection of recipes in the model
	 * @return
	 */
	public HashMap<String, Recipe> getRecipes() {
		return recipes;

	}

	/**
	 * Puts a collection of recipes in the model
	 * @param recipes
	 */
	public void setRecipes(HashMap<String, Recipe> recipes) {
		this.recipes = recipes;
	}

	/**
	 * Returns a recipe with the given name
	 * @param string Name of recipe
	 */
	public Recipe getRecipe(String string) {
		return getRecipes().get(string);
	}
	/**
	 * createPlan used when to create the first plan after a specified recipe
	 * @param name
	 * @return
	 */
	public Plan createPlan(String name) throws PlanningFailedException {
		
		Recipe rec = getRecipe(name);
//		System.out.print(rec.getIngredients());
		
		plan = agent.plan(rec.getIngredients(),rec.getDish());
	
		actions = plan.linearize();
		
		setChanged();
		notifyObservers();
		
		return plan;
	}
	/**
	 * creatPlan used when to create a plan after a failure.
	 * @param initialState
	 * @param goalState
	 * @return
	 */

	public Plan createPlan(HashSet<Predicate> initialState, HashSet<Predicate> goalState) throws PlanningFailedException {
        	plan = agent.plan(initialState,goalState);
		
		actions = plan.linearize();
		
		setChanged();
		notifyObservers();
		return plan;
	}
	
 	public Plan getPlan() {
		return plan;
	}
		
	public Vector<Action> getActions(){
		return actions;
	}
	
	public Agent getAgent() {
		return agent;
	}
	
	/**
	 * Handles case when specified action has failed
	 */
	public void actionFailed(Action failedAction, String recipe) throws PlanningFailedException {
		if((failedAction.getName().compareTo("Finish")!=0)){
			//HashSet<Predicate> deleteList =   failedAction.getDeleteList();
			Plan plan = getPlan();
			int failedEndTime = failedAction.getStartTime()+ failedAction.getDuration();
			
			//to create a new object (a copy of the startState) we have to do the ugly for-loop below.
			HashSet<Predicate> initialState = new HashSet<Predicate>();
			for(Predicate p:  plan.getInitialState()){
				initialState.add(p);
			}
			
			HashSet<Predicate> goalState = new HashSet<Predicate>();
			for(Predicate p: getRecipe(recipe).getDish()){
				goalState.add(p);
			}
			
			//Vector<Action> lin = plan.linearize();
			Action planAction=null;
			Iterator<Action> it = actions.iterator();

			/*
			 * Every action from linearize() with starting times up to the failedActions starting time, should update initialstate.
			 * the actions that has ended at the time updates with both add and delete lists
			 * 
			 */
			/*
			 * the actions that hasn't finished yet, are collected and will have their new duration times computed   
			 */
			
			
			while( it.hasNext() &&  !failedAction.equals(planAction) ){
				planAction = it.next();
				HashSet<Predicate> add = planAction.getAddList();
				HashSet<Predicate> del = planAction.getDeleteList();

				if(!planAction.equals(failedAction)){

					/*
					 * For everything in the add and delete list change the initial state accordingly 
					 */
					
					/*
					 * If the action is still ongoing when the failed action ends
					 * then ad the action to the list and set the new duration time, to the time it still has left  
					 */
					int actionEndTime = planAction.getStartTime() + planAction.getDuration();
					if(actionEndTime > failedEndTime){
						planAction.setDuration(actionEndTime - failedEndTime);
						
						//changeDuration changes the durations of the predicates in the goalstate 
						changeDuration(goalState,planAction.getAddList(),planAction.getDuration());		
						
					}
					else{
					
						for(Predicate p : add){
							initialState.add(p);
						}
				 
						for(Predicate p : del){
							//initialState.removeAll(Collections.singletonList(p));
							initialState = SetHelper.remove(initialState, p);
		
						}
					}
				}
				else{
					for(Predicate p : del){
						//initialState.removeAll(Collections.singletonList(p));
						initialState = SetHelper.remove(initialState, p);
					
					}
				}
				
			}		
			createPlan(initialState, goalState);

		} else{
			this.createPlan(recipe);
		}

	}
	
	private void changeDuration(Collection<Predicate> goalState,HashSet<Predicate> addlist,int duration){
		for (Predicate p : goalState){
			changeDuration(p,addlist,duration);
		}
	}
	private void changeDuration(Predicate goalPred,HashSet<Predicate> addlist,int duration){
		Iterator<Predicate> iter = addlist.iterator();
		
		
		
		while (iter.hasNext()) {
			Predicate pred = iter.next();
			
			if (pred.equals2(goalPred)){
				goalPred.SetDuration(duration);
			}
		}
		if(!goalPred.getObjects().isEmpty()){
			changeDuration(goalPred.getObjects(),addlist,duration);
		}		
	}
}